// 控制正转： 整个过程都是我自己来控制的
// 控制反转： 失去了控制权，控制权被反转了

// ioc 解除耦合 ， 耦合度降低 代码更方便调试和测试， 代码间的依赖关系

// interface Monitor {
//   display(): void;
// }
// interface Host {
//   start(): void;
// }

// class Monitor23inch implements Monitor {
//   display() {
//     console.log("显示开启");
//   }
// }
// class AppleHost implements Host {
//   start() {
//     console.log("开机");
//   }
// }

// class Computer {
//   //   public monitor: Monitor;
//   //   public host: Host;
//   constructor(public monitor: Monitor, public host: Host) {
//     // this.monitor = new Monitor23inch();
//     // this.host = new AppleHost();
//   }
//   bootstrap() {
//     this.monitor.display();
//     this.host.start();
//   }
// }
// 特点灵活，但是创建过程需要自己手动的new 很麻烦
// const monitor = new Monitor23inch();
// const host = new AppleHost();
// const computer = new Computer(monitor, host);
// computer.bootstrap();

// class Container {
//   private instances = new Map();
//   bind<T>(key: string, creator: () => T) {
//     if (!this.instances.has(key)) {
//       this.instances.set(key, creator());
//     }
//     return this.instances.get(key) as T;
//   }
//   resolve<T>(key: string): T {
//     return this.instances.get(key) as T;
//   }
// }
// 容器内部会维护创建的实例，但是目前的缺点就是依赖需要自己来管理
// const container = new Container();
// container.bind<Monitor>("Monitor", () => new Monitor23inch());
// container.bind<Host>("Host", () => new AppleHost());
// const computer = container.bind<Computer>(
//   "Computer",
//   () => new Computer(container.resolve("Monitor"), container.resolve("Host"))
// );

// computer.bootstrap();

// 依赖注入 DI , 通过依赖注入的方式让代码变得灵活，而不是死板。 他是ioc的一种实现
import "reflect-metadata";
class Container {
  private instances = new Map();
  public properties = new Map(); // 所有的注入的属性 都维护到 映射表中，解析某个类的时候查找注入的属性即可
  bind<T>(key: string, creator: () => T) {
    if (!this.instances.has(key)) {
      this.instances.set(key, creator());
    }
    return this.instances.get(key) as T;
  }
  resolve<T>(key: string): T {
    //// Computer:monitor =Monitor
    const instance = this.instances.get(key);
    for (let property of this.properties) {
      let [key, ServiceKey] = property;
      let [classKey, propKey] = key.split(":");
      // 这个属性 是当前类需要注入的才注入，否则不需要进行注入操作
      if (instance.constructor.name != classKey) {
        continue;
      }
      // 之前是字符串，现在是类，类用名字来实例化
      const dep = this.resolve(ServiceKey.name ?? ServiceKey); // 依赖的实例
      instance[propKey] = dep; // 自定找到依赖
    }
    return instance as T;
  }
}

const container = new Container();
// provide 就是将当前类放入到容器中
function Provide(key: string) {
  return function (Target: any) {
    container.bind(key ?? Target.name, () => new Target());
  };
}
// 哪个类的某个属性 注入谁的实例  Computer:monitor = Monitor23inch
function Inject(injectKey?: string) {
  // computer.monitor:new Monitor
  return function (target: any, key: string) {
    // Computer:monitor =Monitor
    // 维护注入的信息，稍后创建computer的时候 查找这样的依赖关系 进行依赖注入
    container.properties.set(
      `${target.constructor.name}:${key}`,
      injectKey ?? Reflect.getMetadata("design:type", target, key)
    );
  };
}
interface Monitor {
  display(): void;
}
interface Host {
  start(): void;
}
@Provide("Monitor")
class Monitor23inch implements Monitor {
  display() {
    console.log("显示开启");
  }
}
@Provide("Host")
class AppleHost implements Host {
  start() {
    console.log("开机");
  }
}
@Provide("Computer")
class Computer {
  @Inject("Monitor")
  public monitor!: Monitor23inch;
  @Inject("Host")
  public host!: AppleHost;
  bootstrap() {
    this.monitor.display();
    this.host.start();
  }
}
const computer = container.resolve<Computer>("Computer");
computer.bootstrap();
// ...............................................................................................
{
  //什么是控制正转，控制权是交给自己的，自己来处理整个流程
  //什么是反转，我失去了控制权，全部在内部自己来做的
  // ioc 将所有的创建过程全部交给"容器“来做，可以解决类之间的耦合问题
  /* 
  // 方案一
  // 在类里面初始化实例,太耦合了
  interface Monitor {}
  interface Host {}
  class Monitor27inch implements Monitor {}
  class AppleHost implements Host {}
  class Computer {
    public monitor = new Monitor27inch();
    public host = new AppleHost();
    boostrap() {
      console.log("启动电脑", this);
    }
  }
  const computer = new Computer();
  computer.boostrap(); */
  /*   
  //  方案二
  // 大量传参麻烦
  interface Monitor {}
  interface Host {}
  class Monitor27inch implements Monitor {}
  class AppleHost implements Host {}
  class Computer {
    constructor(public monitor: Monitor, public host: Host) {}
    boostrap() {
      console.log("启动电脑", this);
    }
  }
  let monitor = new Monitor27inch();
  let host = new AppleHost();
  const computer = new Computer(monitor, host);
  computer.boostrap(); */
  /*   
  //  方案三
  interface Monitor {}
  interface Host {}
  class Monitor27inch implements Monitor {}
  class AppleHost implements Host {}
  class Computer {
    constructor(public monitor: Monitor, public host: Host) {}
    boostrap() {
      console.log("启动电脑", this);
    }
  }
  // 处理容器
  class Container {
    // 存储所有的实例
    private instances = new Map();
    // 存储所有的实例
    bind<T>(key: string, creator: () => T) {
      if (!this.instances.has(key)) {
        this.instances.set(key, creator());
      }
    }
    // 获取对应的实例
    resolve(key: string) {
      return this.instances.get(key);
    }
  }
  const container = new Container();
  container.bind<Monitor>("Monitor", () => new Monitor27inch());
  container.bind<Host>("Host", () => new AppleHost());
  container.bind<Computer>(
    "Computer",
    () => new Computer(container.resolve("Monitor"), container.resolve("Host"))
  ); */
  // 提供到容器中，自动会创建实例在容器中
  interface Monitor {}
  interface Host {}
  class Container {
    // 存储所有的实例
    private instances = new Map();
    // 存放属性对应的信息
    public properties = new Map();
    // 存储所有的实例
    bind<T>(key: string, creator: () => T) {
      if (!this.instances.has(key)) {
        this.instances.set(key, creator());
      }
    }
    // 获取对应的实例
    resolve<T>(key: string): T {
      let instance = this.instances.get(key);
      // 将记录的属性自动注入到当前的实例上
      for (let prop of this.properties) {
        // prop ===>  Computer-monitor : Monitor
        let [key, ServiceKey] = prop;
        let [className, propName] = key.split("");
        // 检索是否是当前的类要进行注入
        if (instance.constructor.name !== className) {
          continue;
        }
        // 当前 实例要进行注入
        // 将主机和显示器自动赋予到实例上
        instance[propName] = this.resolve(ServiceKey);
      }
      return instance;
    }
  }
  const container = new Container();
  @Provide("Monitor")
  class Monitor27inch implements Monitor {}
  @Provide("AppleHost")
  class AppleHost implements Host {}
  // DI 依赖注入，不需要在类中硬编码@Provide("Computer")
  class Computer {
    @Inject("'Monitor") monitor!: Monitor27inch;
    @Inject("Host") Host!: AppleHost;
    bootstrap() {
      console.log("启动", this);
    }
  }

  // key 可传,可不传
  function Provide(key: string) {
    return (target: any) => {
      container.bind(key, () => new target());
    };
  }
  // 哪个类的某个属性 注入谁的实例  Computer:monitor = Monitor23inch
  function Inject(injectKey?: string) {
    // computer.monitor:new Monitor
    return function (target: any, key: string) {
      // Computer:monitor =Monitor
      // 维护注入的信息，稍后创建computer的时候 查找这样的依赖关系 进行依赖注入
      // 当前在哪个原型上注入哪些属性，作为一个映射关系，然后解析电脑实例的时候，自己解析它所依赖的属性
      container.properties.set(
        `${target.constructor.name}-${key}`,
        injectKey ?? Reflect.getMetadata("design:type", target, key)
      );
      // 关联就是哪个类对应哪个属性，用哪个标识找实例来进行复制
    };
  }
}
